package com.lobstar.xrpc.service.base;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lobstar.xrpc.server.config.ServerConfiguration;
import com.lobstar.xrpc.service.annotation.SMethod;
import com.lobstar.xrpc.service.annotation.SParam;
import com.lobstar.xrpc.service.utils.JsonUtils;


public class ServiceScanner {

	private static final Logger LOGGER = LoggerFactory.getLogger(ServiceScanner.class);
	private static volatile ServiceScanner scanner;
	private Map<String,Invoker> invokeMap;
	private ClassFactory newer;

	public void addClassNewer(ClassFactory newer) {
		this.newer = newer;
	}
	private ServiceScanner() {
		try {
			invokeMap = new HashMap<String,Invoker>();
		}catch(Exception e) {
			LOGGER.error("ServiceScanner",e);
		}
	}
	public Object loadInstance(Class<?> clz) throws Exception {
		if(newer != null) {
			return newer.newInstance(clz);
		}
		return clz.newInstance();
	}

	public void loadClasses(String basePackage) {
		Reflections reflections = new Reflections(new ConfigurationBuilder()
				.setUrls(ClasspathHelper.forPackage(basePackage))
				.setScanners(new MethodAnnotationsScanner()));
		Set<Method> methodSet = reflections.getMethodsAnnotatedWith(SMethod.class);
		for (Method method : methodSet) {
			try {
				Class<?> clz = method.getDeclaringClass();
				Object instance = loadInstance(clz);
				Invoker invoker = new Invoker(instance, method);
				Parameter[] parameters = method.getParameters();
				for (Parameter parameter : parameters) {
					SParam sParam = parameter.getAnnotation(SParam.class);
					if(sParam!=null) {
						invoker.addParam(new Params(parameter,sParam.value()));
					}
				}
				SMethod methodName = method.getAnnotation(SMethod.class);
				invokeMap.put(methodName.value(), invoker);
			} catch (Exception e) {
				LOGGER.error("loadClasses",e);
			}
		}
		LOGGER.info("---invoke map---");
		LOGGER.info(invokeMap.toString());
	}

	public static ServiceScanner initScanner() {
		if(scanner == null) {
			synchronized (ServiceScanner.class) {
				if(scanner == null) {
					scanner = new ServiceScanner();
				}
			}
		}
		return scanner;
	}
	public Map<String,Invoker> getInvokeMap() {
		return invokeMap;
	}

	class Params {
		Parameter parameter;
		String name;

		public Params(Parameter parameter,String name) {
			this.parameter = parameter;
			this.name = name;
		}

		public Parameter getParameter() {
			return parameter;
		}

		public void setParameter(Parameter parameter) {
			this.parameter = parameter;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}


	}

	class Invoker {
		Object instance;
		Method method;
		List<Params> paramList;

		public Invoker(Object obj,Method method) {
			this.instance = obj;
			this.method = method;
			this.paramList = new ArrayList<Params>();
		}
		public Invoker addParam(Params name) {
			this.paramList.add(name);
			return this;
		}
		public Object invoke() {
			try {
				return method.invoke(instance);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				throw new RuntimeException(e);
			}
		}
		public Object invoke(Map<String,Object> paramMap) throws Exception {
			try {
				if(this.paramList.size() == 0) {
					return method.invoke(instance);
				}
				Object[] params = new Object[paramList.size()];
				for(int i=0;i<paramList.size();i++) {
					Params p = paramList.get(i);
					Object pv = paramMap.get(p.getName());
					if(pv == null) {
						pv = paramMap.get(String.valueOf(i));
					}
					if(pv != null) {
						Parameter parameter = p.getParameter();
						if(parameter.getParameterizedType() == pv.getClass()) {
							params[i] = pv;
							continue;
						}else {
							try {
								Object pm = JsonUtils.toObject((Map)pv, (Class)parameter.getParameterizedType());
								params[i] = pm;
								continue;
							}catch(Exception e) {
								e.printStackTrace();
							}
						}
					}
					params[i] = null;
				}
				return method.invoke(instance, params);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				throw e;
			}
		}
	}
}

